#include "rtp/RtpStream.h"
#include "rtp/RtpPacket.h"
#include "deplibUV.h"
#include "rtp/RtxStream.h"
#include "utils.h"

namespace Rtp {

    static constexpr uint16_t MaxDropout { 3000 };
    static constexpr uint16_t MaxMisorder { 1500 };
    static constexpr uint32_t RtpSeqMod { 1 << 16 };
    static constexpr size_t ScoreHistogramLength { 24 };
    
    RtpStream::RtpStream(Rtp::RtpStream::Listener *listener, 
        Rtp::RtpStream::Params& params, uint8_t initialScore): listener(listener), params(params), score(initialScore), activeSinceMs(DepLibUV::GetTimeMs()) {}
    
    RtpStream::~RtpStream() {
        delete this->rtxStream;
        this->rtxStream = nullptr;
    }

    void RtpStream::SetRtx(uint8_t payloadType, uint32_t ssrc) {
        this->params.rtxPayloadType = payloadType;
        this->params.ssrc = ssrc;

        if (HasRtx()) {
            delete this->rtxStream;
            this->rtxStream = nullptr;
        }

        Rtp::RtxStream::Params params;
        params.ssrc = ssrc;
        params.payloadType = payloadType;
        params.mimeType.type = GetMimeType().type;
        params.mimeType.subtype = Rtp::RtpCodecMimeType::Subtype::RTX;
        params.clockRate = GetClockRate();
        params.rrid = GetRid();
        params.cname = GetCname();

        params.mimeType.UpdateMimeType();

        this->rtxStream = new Rtp::RtxStream(params);
    }

    bool RtpStream::ReceiveStreamPacket(Rtp::RtpPacket *packet) {
        const uint16_t seq = packet->GetSequenceNumber();

        if (!this->started) {
            InitSeq(seq);

            this->started = true;
            this->maxSeq = seq - 1;
            this->maxPacketTs = packet->GetTimestamp();
            this->maxPacketMs = DepLibUV::GetTimeMs();
        }

        if (!UpdateSeq(packet)) {
            return false;
        }

        if (Utils::Number<uint32_t>::IsHigherThan(packet->GetTimestamp(), this->maxPacketTs)) {
            this->maxPacketTs = packet->GetTimestamp();
            this->maxPacketMs = DepLibUV::GetTimeMs();
        }

        return true;
    }

    inline void RtpStream::InitSeq(uint16_t seq) {
        this->baseSeq = seq;
        this->maxSeq = seq;
        this->badSeq = RtpSeqMod + 1;
    }

    bool RtpStream::UpdateSeq(Rtp::RtpPacket *packet) {
        const uint16_t seq = packet->GetSequenceNumber();
        const uint16_t udelta = seq - this->maxSeq;

        if (udelta < MaxDropout) {
            if (seq < this->maxSeq) {
                this->cycles += RtpSeqMod;
            }
            this->maxSeq = seq;

            if (Utils::Number<uint32_t>::IsLowerThan(packet->GetTimestamp(), this->maxPacketTs)) {
                this->maxPacketTs = packet->GetTimestamp();
                this->maxPacketMs = DepLibUV::GetTimeMs();
            }
        } else if (udelta <= RtpSeqMod - MaxMisorder) {
            if (seq == this->badSeq){ 
                InitSeq(seq);

                this->maxPacketTs = packet->GetTimestamp();
				this->maxPacketMs = DepLibUV::GetTimeMs();

				// Notify the subclass about it.
				UserOnSequenceNumberReset();
            } else {
                this->badSeq = (seq + 1) & (RtpSeqMod - 1);

                this->packetsDiscarded++;

			    return false;
            }
        } else {
            
        }
        return true;
    }
    
    void RtpStream::Pause() {}

    void RtpStream::Resume() {}

    void RtpStream::ResetScore(uint8_t score, bool notify) {
        this->scores.clear();

        if (this->score != score) {
            auto previousScore = this->score;
            this->score = score;

            if (previousScore == 0u) {
                this->activeSinceMs = DepLibUV::GetTimeMs();
            }

            if (notify) {
                this->listener->OnRtpStreamScore(this, score, previousScore);
            }
        }
    }

    void RtpStream::PacketRetransmitted(Rtp::RtpPacket* packet) {
        this->packetsRetransmitted ++;
    }

	void RtpStream::PacketRepaired(Rtp::RtpPacket* packet) {
        this->packetsRepaired ++;
    }
}